package remote

import (
	"apedge/dao"
	"apedge/dto"
	"apedge/pkg/configs"
	"apedge/pkg/protocol"
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"time"

	"github.com/apulis/go-business/pkg/jobscheduler"
	"github.com/mitchellh/mapstructure"
	uuid "github.com/satori/go.uuid"
)

type NpuApedgeInference struct{}

func (infer *NpuApedgeInference) Creat(info dao.ApedgeInference, snInfo dao.ApedgeServiceNode) (*jobscheduler.CreateJobRsp, error) {
	var err error
	job, err := infer.InitJob(info, snInfo)
	if err != nil {
		return nil, err
	}
	rsp, err := CreateJobToJobScheduler(*job)
	return rsp, err
}

func (infer *NpuApedgeInference) InitJob(info dao.ApedgeInference, snInfo dao.ApedgeServiceNode) (*jobscheduler.Job, error) {

	job, err := infer.InitJobBase(info, snInfo)
	if err != nil {
		return nil, err
	}
	err = infer.SetJobMounts(job, info)
	if err != nil {
		return nil, err
	}
	err = infer.SetEnvs(job, info)
	if err != nil {
		return nil, err
	}
	return job, nil
}

func (infer *NpuApedgeInference) InitJobBase(info dao.ApedgeInference, snInfo dao.ApedgeServiceNode) (*jobscheduler.Job, error) {
	job := jobscheduler.NewJob()

	modId, err := strconv.Atoi(configs.Config.Relevant.ModId)
	if err != nil {
		return nil, err
	}
	job.SetModId(modId)
	job.SetJobId(snInfo.ServiceNodeId)
	job.SetOwner("ap")
	job.SetResType(jobscheduler.RESOURCE_TYPE_DEPLOYMENT)
	job.SetImage(fmt.Sprintf("%s:%s", info.ContainerImage, info.ContainerImageTag))
	job.SetNamespace("default")
	job.SetReplicas(1)
	job.SetLabels(map[string]string{
		"app": info.Name,
	})

	nodeSelector := make(map[string]string)

	// nodeSelector["apedgenode"] = "node-1630986721356856924"
	nodeSelector["apedgenode"] = snInfo.NodeId
	job.SetNodeSelector(nodeSelector)

	to := jobscheduler.Toleration{}
	to.SetKey("apedgenode").SetValue("active").SetOperatorEqual().SetTaintEffectNoExecute()
	tolerations := []jobscheduler.Toleration{}
	tolerations = append(tolerations, to)
	job.SetTolerations(tolerations)

	annotation := make(map[string]string)
	annotation["sidecar.istio.io/inject"] = "false"
	job.SetAnnotation(annotation)

	// 端口映射
	ports := dto.ContainerPorts{}
	err = json.Unmarshal([]byte(info.ContainerPorts), &ports)
	if err != nil {
		return nil, err
	}
	cPorts := []jobscheduler.ContainerPort{}
	for k, v := range ports.Infer {
		kInt, err := strconv.Atoi(k)
		if err != nil {
			return nil, err
		}
		vInt, err := strconv.Atoi(v)
		if err != nil {
			return nil, err
		}
		p := jobscheduler.ContainerPort{
			Port:     kInt,
			HostPort: vInt,
			PortName: "inference-port",
			// ServiceName: snInfo.ServiceNodeId,
		}
		cPorts = append(cPorts, p)
	}
	for k, v := range ports.Others {
		kInt, err := strconv.Atoi(k)
		if err != nil {
			return nil, err
		}
		vInt, err := strconv.Atoi(v)
		if err != nil {
			return nil, err
		}
		uuids, err := uuid.NewV4()
		if err != nil {
			return nil, err
		}
		p := jobscheduler.ContainerPort{
			Port:       kInt,
			TargetPort: vInt,
			PortName:   uuids.String(),
			// ServiceName: snInfo.ServiceNodeId,
		}
		cPorts = append(cPorts, p)
	}
	job.SetContainerPorts(cPorts)

	return job, nil
}

func (infer *NpuApedgeInference) SetJobMounts(jobRequest *jobscheduler.Job, info dao.ApedgeInference) error {
	deviceType := "NPU"
	err := infer.SetInitContainer(jobRequest, info)
	if err != nil {
		return err
	}

	// 挂载相关的库 NPU
	if deviceType == "NPU" {
		jobRequest.MountPoints = AddAppMountPoint(jobRequest.MountPoints, "file://"+"/usr/local/Ascend/driver",
			"/usr/local/Ascend/driver",
			false,
		)
		jobRequest.MountPoints = AddAppMountPoint(jobRequest.MountPoints, "file://"+"/usr/local/bin/npu-smi",
			"/usr/local/bin/npu-smi",
			false,
		)
		// 挂载sdk到推理容器
		jobRequest.MountPoints = AddAppMountPoint(jobRequest.MountPoints,
			"file://"+INFER_DATA_SDK_PATH_APP,
			INFER_DATA_SDK_PATH_APP,
			false,
		)
	}
	infer.AddAplowResource(jobRequest, info)
	return nil
}

func (infer *NpuApedgeInference) SetEnvs(job *jobscheduler.Job, info dao.ApedgeInference) error {
	envMap := make(map[string]string)
	err := json.Unmarshal([]byte(info.Envs), &envMap)
	if err != nil {
		return err
	}
	envMap["SERVICE_ID"] = info.ServiceId
	job.SetEnvs(envMap)

	return nil
}

func AddAppMountPoint(mounts []jobscheduler.MountPoint, subPath string, containerPath string, readOnly bool) []jobscheduler.MountPoint {
	return append(mounts, jobscheduler.MountPoint{
		Path:          subPath,
		ContainerPath: containerPath,
		ReadOnly:      readOnly,
	})
}

func CreateJobToJobScheduler(job jobscheduler.Job) (*jobscheduler.CreateJobRsp, error) {
	var createJobReq jobscheduler.CreateJobReq
	var createJobRsp jobscheduler.CreateJobRsp
	var client = &http.Client{
		Timeout: time.Second * 10,
	}

	createJobReq = jobscheduler.CreateJobReq{Job: job}
	data, err := json.Marshal(createJobReq)
	if err != nil {
		return nil, err
	}
	logger.Info("CreateJobScheduler JSON: ", string(data))

	jobUrl := JobSchedulerEdgeInfer
	req, err := http.NewRequest("POST", jobUrl, bytes.NewBuffer(data))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := client.Do(req)

	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var respBody protocol.APISuccessRsp
	err = json.NewDecoder(resp.Body).Decode(&respBody)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("job-scheduler create error %d %s", resp.StatusCode, respBody.Msg)
	}
	err = mapstructure.Decode(respBody.Data, &createJobRsp)
	if err != nil {
		return nil, err
	}
	logger.Infof("HTTP StatusCode: %d", resp.StatusCode)
	logger.Infof("CreateJobScheduler Response: %+v", createJobRsp)
	return &createJobRsp, nil
}

func (apSvc *NpuApedgeInference) AddAplowResource(job *jobscheduler.Job, info dao.ApedgeInference) {
	resource := jobscheduler.ResourceQuota{

		Request: jobscheduler.ResourceData{
			Device: jobscheduler.Device{
				DeviceType: "npu.huawei.com/NPU",
				DeviceNum:  "1",
				// Series:      configs.Config.Base.Device.Series,
				ComputeType: "huawei_npu",
			},
		},
		Limit: jobscheduler.ResourceData{
			// CPU:    strconv.FormatFloat(info.Cpu, 'f', 2, 64),
			// Memory: strconv.FormatInt(info.Mem, 10),
			Device: jobscheduler.Device{
				DeviceType: "npu.huawei.com/NPU",
				DeviceNum:  "1",
				// Series:      configs.Config.Base.Device.Series,
				ComputeType: "huawei_npu",
			},
		},
	}
	job.SetQuota(resource)
}

func (infer *NpuApedgeInference) SetInitContainer(jobRequest *jobscheduler.Job, info dao.ApedgeInference) error {
	var err error
	var modelList []dao.ApflowModel
	var initCountainerMountsPoint []jobscheduler.MountPoint
	var initCmd string
	deviceType := "NPU"

	err = json.Unmarshal([]byte(info.Models), &modelList)
	if err != nil {
		return err
	}

	// 1. 挂载模型的文件目录
	for _, m := range modelList {
		initCountainerModelPathApp := fmt.Sprintf("%s/%d_%d", INFER_DATA_MODELS_PATH_APP, m.ID, m.VersionId)
		fileXXX := fmt.Sprintf("%s/%d", initCountainerModelPathApp, m.VersionId)
		initCountainerMountsPoint = append(initCountainerMountsPoint, jobscheduler.MountPoint{
			Path:          "file://" + initCountainerModelPathApp,
			ContainerPath: initCountainerModelPathApp,
			ReadOnly:      false,
		})
		jobRequest.MountPoints = AddAppMountPoint(jobRequest.MountPoints,
			"file://"+initCountainerModelPathApp+"/pkg",
			INFER_DATA_M_PATH_APP,
			false)
		modeDownloadLink := m.DownloadLink
		// testModelsUri := "http://192.168.3.137/file-server/api/v1/files/cdab401f8e91622e753572e3dd87acfe"
		initCmd += fmt.Sprintf("wget %s -P /tmp -O /tmp/models.tar.gz && mkdir -p  %s/temp && rm -r %s/* && tar -zxvf /tmp/models.tar.gz -C %s && mv %s %s/pkg && ", modeDownloadLink, initCountainerModelPathApp, initCountainerModelPathApp, initCountainerModelPathApp, fileXXX, initCountainerModelPathApp)
	}

	if deviceType == "NPU" {
		// 推理 sdk npu
		initCountainerSdkPathApp := INFER_DATA_SDK_PATH_APP
		initCountainerMountsPoint = append(initCountainerMountsPoint, jobscheduler.MountPoint{
			Path:          "file://" + initCountainerSdkPathApp,
			ContainerPath: initCountainerSdkPathApp,
			ReadOnly:      false,
		})
		initCmd += fmt.Sprintf("wget %s -P /tmp -O /tmp/ai_sdk.tar.gz && mkdir -p %s/do && rm -r %s/* && tar -zxvf /tmp/ai_sdk.tar.gz -C %s && ", SdkUri, initCountainerSdkPathApp, initCountainerSdkPathApp, initCountainerSdkPathApp)
	}

	initContainer := &jobscheduler.Container{
		ImageName:   fmt.Sprintf("%s:%s", configs.Config.Relevant.Image.InitContainer, configs.Config.Relevant.Image.InitContainerTag),
		MountPoints: initCountainerMountsPoint,
	}

	initContainer.Cmd = append(initContainer.Cmd, "sh")
	initContainer.Cmd = append(initContainer.Cmd, "-c")
	initCmd += "echo InitCmdOk"
	initContainer.Cmd = append(initContainer.Cmd, initCmd)
	jobRequest.SetInitContainer(initContainer)

	return nil
}

// 删除服务
func (apSvc *NpuApedgeInference) DeleteService(jobId string, deviceType string) (dto.APISuccessResp, error) {
	return DeleteJobToJobScheduler(jobId)
}

func DeleteJobToJobScheduler(jobId string) (dto.APISuccessResp, error) {
	var deleteJobRsp dto.APISuccessResp
	var client = &http.Client{
		Timeout: time.Second * 10,
	}

	logger.Info("job scheduler start to delete job", jobId)
	jobUrl := JobSchedulerEdgeInfer
	jobUrl = jobUrl + "/" + jobId
	req, err := http.NewRequest("DELETE", jobUrl, nil)
	if err != nil {
		return deleteJobRsp, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := client.Do(req)
	if err != nil {
		return deleteJobRsp, err
	}
	defer resp.Body.Close()

	err = json.NewDecoder(resp.Body).Decode(&deleteJobRsp)
	if err != nil {
		return deleteJobRsp, err
	}
	logger.Info("job scheduler delete job", jobId, " response:", deleteJobRsp)
	switch resp.StatusCode {
	case 200:
		return deleteJobRsp, nil
	default:
		if deleteJobRsp.Code == 270000003 {
			return deleteJobRsp, nil
		}
		return deleteJobRsp, fmt.Errorf("job-scheduler error %d %s", resp.StatusCode, deleteJobRsp.Msg)
	}
}
